TechNote - Hyperlinks

June 11, 2026

Last updated on August 24, 2026

See also the further Analysis and Conclusions below…

 

OneNote lets users create hyperlinks to notebooks, sections, pages, and paragraphs, all with a simple right-click. These hyperlinks can be pasted into any OneNote page as a way of creating relationships and mind maps between pages across notebooks and sections.

 

But those hyperlinks are opaque - there is no publicly available documentation that explains how OneNote generates hyperlinks. Unfortunately, the IDs used in hyperlinks have no obvious correlation to the IDs provided by the OneNote COM API through GetHierarchy or GetPageContent, making it impossible to reverse-engineer or map a hyperlink to the well-known XML metadata normally used. And worse, those URIs differ from the URIs returned by the GetHyperlinkToObject API.

 

Two ID Namespaces

The OneNote COM API maintains two distinct identifier spaces that don't have a documented transformation between them.

 

Hierarchy/page XML IDs look like this:

 

{5A643683-78AF-00B9-2CF8-5870571CB377}{1}{E194715543909058...}

 

A GUID followed by {1} and a long numeric suffix.

 

While hyperlink URI IDs from GetHyperlinkToObject look like:

 

section-id={02FE7AAF-7BC9-4F97-A7B2-B46ECEBB2BBA}

page-id={8857B2AA-4D63-4A8F-84C9-70A07D0F75EB}

object-id={DB6EF273-8B33-09B3-127A-7339FE620CE6}

 

Plain GUIDs in braces, no suffix. The IDs are in the same general GUID format but the suffixes present in the XML hierarchy IDs ({1}{B0}, long numeric strings) are stripped in the hyperlink representation. Microsoft has never documented the relationship between these two forms.

 

Translation Techniques

 

Forward Map (Heavy)

OneMore has been using a forward map for a very long time. In this approach, we build a map by calling GetHyperlinkToObject on every object we care about within a defined scope and storing the association in an ephemeral cache.

 

  • Enumerate the hierarchy via GetHierarchy to get all section/page XML IDs
  • For each page, call GetPageContent to get object/OE IDs
  • Call GetHyperlinkToObject(pageId, objectId, ...) for each
  • Store the result in a dictionary to map {hyperlink-URI-GUID} → {xml-id}

 

This approach is generally reliable (if you can keep the COM connection stable for the lifetime of the probe phase). But it is also extremely expensive, long enough that users notice and have to wait while watching a progress bar slowly increment.

 

Given a hyperlink URI, you could use that to NavigateTo that target, read the relevant IDs, and then navigate back to the original location. This is slow, visually surprising, and not always practical when working with more than one URI.

 

Targeted Lookup (Best, but with caveats!)

Hyperlink URIs have two forms - local and remote - and are generally consistent in their structure.

 

When the user right-clicks a paragraph in a notebook hosted in OneDrive and chooses Copy Link to Paragraph, the clipboard will contain a Text entry with two lines that looks something like (modified)

 

https: //onedrive.live.com/view.aspx?resid=<private>%2158599&id=documents&wd=target%28Demo%20Section%20Group%2FDemo%20Section.one%7CA31F01F5-B99C-4F24-B6BF-D95D3A71EABF%2FDemo%20Page%7C9A94BD92-C7A1-418D-ABD9-20FB70B697AA%2F%29&wdpartid={ED9D3A77-B166-45C4-88D6-B8E840BF3152}{1}&wdsectionfileid=6925D0374517D4B4!58610&end

onenote: https: //d.docs.live.net/<private>/Documents/OneMore%20Smoke%20Tests%20(Read-Only)/Demo%20Section%20Group/Demo%20Section.one#Demo%20Page&section-id={A31F01F5-B99C-4F24-B6BF-D95D3A71EABF}&page-id={9A94BD92-C7A1-418D-ABD9-20FB70B697AA}&end

 

The second line is interesting because it contains the notebook name, section group path, section .one file name, and page name. It may even contain an &object-id parameter, although remember none of these IDs can be correlated to XML IDs.

 

Right-clicking a paragraph in a locally hosted notebook would look something like:

 

onenote: ///\\COHN-SPECTRE\Userssteve\Documents\Local%20Notebooks\Local\open.one#Things%20are%20Great&section-id={E0C21B1E-390C-40BE-8E14-DD84D34ADDDC}&page-id={7FE33A96-E03E-44AE-9159-C84CBBD43C22}&object-id={E79DB3B0-BCEB-0671-3E8E-900E589FE443}&F3

 

We can parse this into its individual parts and use that information.

 

  • COHN-SPECTRE is the local machine name, $env:COMPUTERNAME
  • Userssteve is a combination of Users\steve, pointing to the local $env:USERPROFILE path C:\Users\steve
  • "Local Notebooks" is a folder path in my user profile
  • "Local" is the notebook name
  • "open" is the section name
  • "Things are Great" is the page name

 

The tricky part is that the folder path may be an arbitrary depth. So a parsing algorithm needs to incrementally build a path to the .one file to confirm that the file can be found and, if not, rethink its understanding of what is "path" versus what is "notebook/section-group/section" within the URI.

 

Because the path prior to the notebook name may follow unknown patterns, it may be helpful to let the user specify one or more search paths in Settings as a fallback in case the .one file cannot be found simply by parsing. (TBD)

 

OneNoteLinkParser

This is what's implemented by the OneMore\Helpers\OneNoteLinkParser class. This is used to resolve hyperlink URIs to path names - notebook/section/page.

 

If the URI stops at page-id then we have the page and can easily determine its page ID. This is the ideal case with no forward lookup needed at all.

 

If the URI includes an object-id then we can open the page, and create a breaking forward lookup of objects on the page - iterate each and stop when we get to one that matches the URI. Forward lookup is scoped to a single page.

 

But…! What does this mean in practicality?

Do not rely on Copy Link to Paragraph - that is a dead end.

 

Copy Link to Paragraph will return a hyperlink URI as you would expect. For every paragraph on the page, the URIs include a virtual section-id, page-id, and object-id and only differ by a trailing &XX link terminator, such as the &2F terminator below.

 

Link created by Copy Link to Paragraph:

 

onenote:///\\COHN-SPECTRE\Userssteve\Documents\Local%20Notebooks\Local\open.one#second&section-id={E0C21B1E-390C-40BE-8E14-DD84D34ADDDC}&page-id={504EDBD0-A7DC-4911-90AA-207C82E80ED2}&object-id={C7A61492-E478-05B5-24DE-500863B4266E}&2F

 

There is no way to translate hyperlink URIs to XML section IDs, page IDs and paragraph objectIds. So we have to try getting the hyperlink URI of each using GetHyperlinkToObject and then compare with the selected paragraph's URI.

 

Link created by GetHyperlinkToObject - for the exact same paragraph:

 

onenote:///\\COHN-SPECTRE\Userssteve\Documents\Local%20Notebooks\Local\open.one#second&section-id={E0C21B1E-390C-40BE-8E14-DD84D34ADDDC}&page-id={504EDBD0-A7DC-4911-90AA-207C82E80ED2}&object-id={C7A61492-E478-05B5-24DE-500863B4266E}&30

 

The hyperlinks almost match - the base address, notebook name, section-id, page-id, object-id are all equivalent, except the terminator is always different (2F does not equal 30!) And there's still no way to correlate URIs from GetHyperlinkToObject with those from Copy Link to Paragraph. Even though both full URIs will navigate you to the same paragraph!

 

The object-id  value references the Outline, not any specific OE. It's unclear what the link terminator actually does. Some say it's an offset - but from where? Some think that offset would change if you add or remove content in the Outline. but that's just not the case. So, there's some witchcraft going on behind the scenes, and Microsoft ain't talking.

 

Full disclosure, we could use the URI to navigate to the paragraph, grab the ID, and then navigate back. But that would create a very disruptive visual experience for the user. "But I can live with that!", you might say. No. No, you can't. And I won't allow it. 'nuf said.

 

End of story.

 

So for behaviors such as the Embed command, we can use Copy Link to Page to initiate a copy and then ask for start/end delimiters. Honestly, Copy Link to Paragraph has the same info, so that would work too if we just ignore the object-id part.

 

So, we're stuck with always using delimiters - and never rely on the ID values in hyperlink URIs to be unique or idempotent.

 

 

═══════════════════════════════════════════════════════════════════════════════════════════════════

Analysis

 

This is a sizable effort that will affect multiple existing workflows, track paragraphs in new tables within the OneMore.db, increase the responsibility of the Hashtag scanner (and require it to be enabled), and touch other commands as noted here:

 

The Bookmark, Copy Path to Page, Embed, And Two-way Link commands must all inject a bookmark into the target paragraph - which means Bookmark and Copy Path to Page now become page-update-and-save commands rather than just in-memory constructs - which means they can affect the last modified time and author of the page content like the other two commands! This will hold true even if, for example, Bookmark is never used by a follow up command; it will still update the bookmarked page. If that is an acceptable side effect, then the following is a solution.

 

It may be possible to add a per-notebook enable/disable option. It would be disabled by default, presuming the majority of users have single-user notebooks. It could be enabled for notebooks shared by small teams.

 

Analysis of Three

 

Three threads (my own "Safe Move" idea in #1747, Grant's "Safe Bookmark" in #1747, and Jason's GUID bookmark in #1861) converge cleanly around infrastructure OneMore already has, rather than picking one proposal wholesale.

 

The synthesis: GUID bookmarks, riding the existing hashtag scanner

 

  1. One permanent GUID per bookmarked paragraph, not a history list. Grant's design stores a growing list of old Object IDs in the target paragraph. Jason's design assigns a single immutable GUID. The GUID is strictly better — it never grows, never needs "which old ID is the right one" logic, and is exactly the pattern we already use for omPageID at the page level. So: extend that same Meta-tag mechanism to paragraphs. <one:Meta name="omBookmarkID" content="{guid}"> on the target paragraph, created the first time the user does "Copy Bookmark Link" via OneMore.
  2. The source link carries the GUID as inert payload. Per the Hyperlinks tech note, OneNote doesn't care what's stored in link text/URI as long as it's well-formed — that's the trick both Grant and Jason are exploiting. So the hyperlink OneMore inserts at the "from" location isn't just OneNote's native link — it's OneNote's link plus the bookmark GUID tacked on as opaque data. When the native link resolves, great, nothing to do. When it doesn't (paragraph ID changed), OneMore has the GUID to fall back on.
  3. No new database, no new scan — extend the hashtag scanner we already run. This is the piece that resolves Grant's biggest objection ("I don't want a full-notebook scan every time I move a page"). We already maintain a persistent index across notebooks for #hashtags and page Meta tags. Teach that same background pass to also index omBookmarkID → (page, current paragraph Object ID). That's marginal cost on a sweep that already happens, not a new expensive system.
  4. Resolution is reactive/on-demand, not a mandatory gate on every move. Our original "Safe Move" idea required routing all moves through a OneMore command that proactively patches every inbound link — powerful, but exactly the "tiresome long scan on every move" Grant pushed back on. Instead: leave moves alone. When a user hits a broken link, right-click → Fix Bookmark Link. OneMore reads the GUID payload off the dead link, looks it up in the already-maintained index, jumps to wherever that paragraph now lives, and rewrites the source link in place. Cheap, because it's an index lookup, not a scan.
  5. Keep "Safe Move" as an optional, narrow-scope upgrade. For users who want zero broken links rather than fix-on-demand, offer proactive repair scoped only to bookmarks that live on the page being moved — the index already tells us which GUIDs are on that page, so we don't need to touch the rest of the notebook, just re-resolve links whose GUID matches one of this page's bookmarks. This is our original idea, but bounded so it doesn't have Grant's "full sweep every time" cost.
  6. Self-check comes for free from the same index. Jason's duplicate/orphan detection ("this GUID appears twice," "this GUID has no matching paragraph") is just a validation pass over the same index during its regular sweep — no separate mechanism needed. Could surface in a panel similar to the existing hashtag/search UI.

 

Net result

 

One mechanism — a per-paragraph GUID stored as a Meta tag, propagated as inert payload on links, and maintained by the existing hashtag scanner — covers Grant's "fix it when I hit it" flow, Jason's "bombproof + self-checking bookmark" flow, and our own "Safe Move" flow as three different UI entry points into the same underlying index, rather than three competing implementations.

 

 

Plan

 

GitHub issues #1747 and #1861 both describe the same underlying problem: OneNote hyperlinks to a specific paragraph encode a page-id + object-id, and OneNote silently regenerates a paragraph's object-id when a page is moved or edited elsewhere, breaking any hyperlink that pointed at it. TechNote - Hyperlinks establishes why this can't be fixed by trusting OneNote's own IDs (they're undocumented and not guaranteed stable) and why a full-notebook rescan to eagerly repatch every link on every move is too expensive to do proactively (this was debated at length in the #1747 thread and rejected by both the maintainer and the reporter).

 

The synthesis of both issues: give a linked-to paragraph a permanent, OneMore-owned identity (a GUID anchor) that survives OneNote's own ID churn, and repair links reactively (on demand, when the user hits a broken one) rather than proactively rescanning everything on every move. This plan implements that, reusing three pieces of infrastructure that already exist and already do almost exactly this:

 

  • ReminderLocator already plants a GUID anchor as a nested <one:Meta> inside an <one:OE> to give a paragraph a stable identity across object-id regeneration — same trick, different feature.
  • HashtagScanner already walks every page on a schedule (and on-demand for a single page) and maintains a SQLite catalog — the new anchor index rides this same pass instead of adding a second scan.
  • EmbedCommand.FindParagraphOE already solves the exact "match a stale link's object-id back to a live paragraph" problem, including the non-obvious fact that a <one:OE>'s own objectID XML attribute is not directly comparable to the GUID in a hyperlink's object-id= parameter — the only reliable mapping is through OneNote.GetObjectHyperlink, disambiguated by a per-batch sequence number when the GUID alone is ambiguous.

 

Design

 

  1. Anchor: when the user links to a paragraph (via Copy Link to Paragraph, Bookmark, or Two-Way Link), stamp that paragraph's OE with <one:Meta name="omBookmarkAnchor" content="{guid}">, using the same nested-Meta mechanics as ReminderAnchor. The link itself is generated exactly as today (one.GetHyperlink) — no change to link text/format. The anchor is a side channel, not part of the link.
  2. Index: extend the existing hashtag scan (HashtagScanner.ScanPage) to also find omBookmarkAnchor metas per page. For each one found, call one.GetObjectHyperlink(pageId, xmlObjectId) (already the reliable way to get the URI-form GUID for an OE, per EmbedCommand) to derive (objectGuid, sequence), and persist to a new SQLite table pair in the existing HashtagProvider catalog:
    • bookmark_anchor — current location of each anchor (notebook/section/page/objectID).
    • bookmark_history — every (objectGuid, sequence) ever observed for that anchor. This history is what lets a stale link (whose embedded object-id no longer matches anything live) be bridged forward to the anchor's current location — the same insight #1747 proposed storing inside page XML, kept server-side in the catalog instead.
      No new scan cadence, throttle, or background job — this piggybacks on
      HashtagScanner's existing full and single-page (ScanHashtagsOnPageCommand) passes.
  3. Repair command: new FixBookmarkLinkCommand, invoked by right-click on a (possibly broken) link. Parses the clicked link via the existing OneNoteLinkParser, looks up its (objectGuid, terminator) in bookmark_history, resolves to the anchor's current (pageId, objectId), rebuilds the link via one.GetHyperlink, and rewrites the href in place using the same CDATA/HtmlAgilityPack mutation NameUrlsCommand.SimplifyUrls already uses to rewrite hrefs.

 

Files to add

 

File

Purpose

OneMore/Models/OeMetaAnchor.cs

Generalized GetAnchorId/EnsureAnchor for a nested <one:Meta> anchor on an OE, parameterized by meta name. Extracted from ReminderLocator (its ordering logic — MediaIndex, Tag, OutlookTask, then Meta — is generic OE-schema knowledge, not reminder-specific).

OneMore/Helpers/OneNoteLinkAddress.cs

Extracted from EmbedCommand: ParseOwnSequence, TryParseHex, ExtractLinkAddress, SplitLinkAddress become shared internal static methods instead of EmbedCommand-private, so the new scanner/repair code and EmbedCommand share one implementation.

OneMore/Commands/Tagging/BookmarkPageScanner.cs

Sibling to HashtagPageScanner. Scan(OneNote one, Page page) walks OEs (including table cells, mirroring HashtagPageScanner's recursion) for omBookmarkAnchor metas; for each, calls one.GetObjectHyperlink to derive (objectGuid, sequence). Read-only — does not mutate the page.

OneMore/Commands/References/FixBookmarkLinkCommand.cs

The repair command (detail below).

Tests: OneMoreTests/Models/OeMetaAnchorTests.cs, OneMoreTests/Helpers/OneNoteLinkAddressTests.cs, OneMoreTests/Commands/Tagging/BookmarkPageScannerTests.cs, OneMoreTests/Commands/References/FixBookmarkLinkCommandTests.cs

Pure-logic coverage (no COM), following EmbedCommandTests.cs's pattern of testing static/internal helpers directly against hand-built XElement fixtures.

 

Files to modify

 

  • OneMore/Models/MetaNames.cs — add BookmarkAnchor = "omBookmarkAnchor".
  • OneMore/Commands/Reminders/ReminderLocator.csGetAnchorId/EnsureAnchor become thin forwarders to OeMetaAnchor, passing MetaNames.ReminderAnchor. Pure refactor; ReminderLocatorTests.cs must still pass unchanged — do this first as the regression guard.
  • OneMore/Commands/References/EmbedCommand.cs — remove the 4 private static members now in OneNoteLinkAddress; call the shared helper. Pure refactor, no behavior change.
  • OneMore/Commands/Tagging/HashtagsDB.sql — add bookmark_anchor / bookmark_history tables + indexes; bump the catalog version constant used for fresh installs.
  • OneMore/Commands/Tagging/HashtagProvider.cs — add Upgrade5to6 (same shape as Upgrade4to5); add WriteBookmarkAnchor(...), ResolveBookmarkAnchor(objectGuid, sequence), DeletePhantomBookmarkAnchors(...) (called alongside the existing DeletePhantoms cleanup).
  • OneMore/Commands/Tagging/HashtagScanner.cs — in ScanPage(...), call BookmarkPageScanner.Scan alongside the existing hashtag page scanner and write results via the new provider methods. This is an independent side-write; it must not affect the existing hashtag-dirty/return-value logic.
  • OneMore/Commands/References/CopyLinkCommand.cs — in the paragraph-link branch, call OeMetaAnchor.EnsureAnchor(selected, ns, MetaNames.BookmarkAnchor) and persist via one.Update(page). New side effect: this command currently only reads the page and writes the clipboard.
  • OneMore/Commands/References/BookmarkCommand.cs — in MakeBookmark, stamp the anchor on bookmark.Range.Root and persist. Requires threading OneNote one into MakeBookmark (currently only takes Page page) and making it async.
  • OneMore/Commands/References/TwoWayLinkCommand.cs — in CreateLinks, stamp anchors on both candidate and target.Root before the one.Update(...) calls that already exist there — no new update call needed, just insert the anchor stamping before existing persistence.
  • OneMore/AddInCommands.cs, OneMore/Ribbon/RibbonTabHome.xml (and wherever ribCopyLinkToParagraphButton is duplicated for other tabs) — register FixBookmarkLinkCommand as a [Command(...)] method + ribbon button, modeled directly on the existing ribCopyLinkToParagraphButton entry. No change needed to ContextMenuSheet — it auto-discovers [Command]-tagged methods.
  • OneMore/Properties/Resources.resx / Resources.Designer.cs — new button label/tooltip strings and a handful of user-facing error/info strings (never-anchored, target-missing, not-indexed, already-current, etc.). Do not touch translated Resources.xx-XX.resx files.

 

Sequencing

 

  1. Extract OneNoteLinkAddress from EmbedCommand (mechanical, zero behavior change).
  2. Extract OeMetaAnchor from ReminderLocator (mechanical; verify ReminderLocatorTests still pass).
  1. MetaNames.BookmarkAnchor.
  1. SQLite schema + HashtagProvider upgrade + new provider methods.
  1. BookmarkPageScanner + HashtagScanner wiring — indexing must work before anything reads it.
  1. Anchor-planting in CopyLinkCommand / BookmarkCommand / TwoWayLinkCommand.
  1. FixBookmarkLinkCommand + ribbon/resx/AddInCommands wiring.
  1. Unit tests alongside each step as it lands; steps 1-5 don't need the ribbon wiring to be testable.

 

Key technical detail (verified against EmbedCommand)

 

A <one:OE>'s own objectID XML attribute (e.g. {1E1BC2E4-...}{45}{B0}) is not directly comparable to the GUID in a hyperlink's object-id={...} parameter — EmbedCommand.FindParagraphOE's doc comment states the mapping is only reliable through OneNote.GetObjectHyperlink. So BookmarkPageScanner must derive objectGuid by calling one.GetObjectHyperlink(pageId, xmlObjectId) and parsing that URI (via the shared OneNoteLinkAddress helper), not by regexing the raw XML attribute. The XML attribute's own decimal middle segment (ParseOwnSequence) is still used, but only as the tie-breaker sequence number when a GUID match is ambiguous — exactly mirroring FindParagraphOE's existing algorithm, just resolved against DB rows at repair time instead of live OEs at paste time.

 

Edge cases

 

  • Anchored paragraph deleted entirely: not detected proactively; caught at repair time when the resolved OE no longer exists on the target page → clear error, no crash, no page write.
  • Same-page copy/paste duplicates an anchor: BookmarkPageScanner picks the OE with the greatest lastModifiedTime as canonical and logs the collision; unlike ReminderLocator, do not replicate live split-on-collision during a background scan (that mutates the page unattended, which no existing hashtag-scan pass does today). Flag as a known limitation, fixable later by re-running Copy Link/Bookmark on the duplicate with forceNew: true.
  • Link never anchored: ResolveBookmarkAnchor returns nothing → clear "can't fix, no bookmark was ever set here" message.
  • Notebook excluded from hashtag scanning: checked explicitly (mirrors ScanHashtagsOnPageCommand's Included check) before attempting resolution.
  • Catalog doesn't exist yet (fresh install, scanner never run): checked via existing HashtagProvider.CatalogExists().
  • Page itself moved to a different section/notebook (not just the paragraph's object-id changing): handled naturally since bookmark_anchor/bookmark_history lookups are keyed by objectGuid globally, not scoped to the stale link's original page-id.
  • Anchor-write failures in CopyLinkCommand/BookmarkCommand/TwoWayLinkCommand (COM error on one.Update): log but don't block the command's primary success path (clipboard copy / bookmark set) — the anchor is best-effort insurance, not the deliverable.

 

Verification

 

  • Unit tests (no COM) for: OeMetaAnchor anchor create/reuse/ordering; OneNoteLinkAddress GUID/sequence/terminator parsing; BookmarkPageScanner single/table-cell/duplicate/none cases; the anchor resolution tie-break logic in FixBookmarkLinkCommand (largest sequence ≤ target wins; single candidate; no-sequence fallback; empty result). Run existing ReminderLocatorTests and EmbedCommandTests to confirm the two extraction refactors are behavior-preserving.
  • Manual, with real OneNote (document as a Test Protocol block per EmbedCommandTests.cs convention, since none of this can be exercised without live COM/OneNote):
    1. Copy Link to Paragraph on a page; confirm omBookmarkAnchor Meta appears in that OE and the page was saved.
    2. Bookmark + Two-Way Link between two paragraphs on different pages; confirm both ends get anchors.
    3. Run/await a hashtag scan covering the anchored pages; confirm bookmark_anchor/bookmark_history rows appear.
    4. Force the target paragraph's object-id to regenerate (edit heavily / sync from a second client); rescan; confirm the anchor's row updates and history gains a new entry.
    5. Paste the original (now-stale) link elsewhere, click into it, run "Fix Bookmark Link"; confirm the href is rewritten and navigates correctly.
    6. Move the target page to a different section; rescan; repeat step 5 to confirm cross-section resolution.
    7. Delete the target paragraph; run "Fix Bookmark Link" on the stale link; confirm a graceful message, not a crash.
    8. Run "Fix Bookmark Link" on a plain never-anchored link, and on a notebook excluded from scanning; confirm the respective clear messages.

 

Non-goals (explicitly out of scope)

 

The proactive "Safe Move" command, a "what links here" browser/graph feature, and any UI to audit all bookmarks. This plan is limited to: anchor-planting, incremental indexing via the existing scanner, and on-demand repair.

 

 

═══════════════════════════════════════════════════════════════════════════════════════════════════

Conclusions

 

Does this work for OneNote Links?

 

Anchoring only happens in three OneMore commands — CopyLinkCommand (OneMore's own "Copy Link to Paragraph"/"Copy Link to Page"), BookmarkCommand, and TwoWayLinkCommand. Each of these calls OeMetaAnchor.EnsureAnchor(...) on the target paragraph at the moment the link is created. OneNote's own native "Copy Link to Paragraph" (right-click → Copy Link to Paragraph, built into OneNote itself) never touches OneMore code at all — it's OneNote generating and copying the link directly. So no anchor Meta ever gets planted on that paragraph.

 

Closing this gap means piggybacking on HashtagService to also scan for native onenote:// links and add an omBookmarkAnchor Meta to each, record it in the database.

 

Regardless, when a page is moved, you need to manually call Fix Bookmark Link on both that page and every target page that references it or that it references.

 

Is it worth it?

 

I'm leaning towards no. This is a ton of work, with a lot of gaps, and a lot of manual management needed by the user to keep it all in sync.

 

Way forward

 

I'd like to go back to basics, understand the exact cases where links break, when they don't break. I have trouble breaking them to be honest! But there's only one of me so it's hard to emulate a team.

 

 

#omwiki #omdeveloper #omtechnote

 

© 2020 Steven M Cohn. All rights reserved.

Please consider a sponsorship or one-time donation to support ongoing development

 

 

Created with OneNote.